﻿using System;
using System.IO;                    // FileReader and such
using System.Collections;           // ArrayLists and such 
using System.Collections.Generic;
using System.Text;
using Tedds_Tool.Instructions;
using Tedds_Tool.Compilers.LEET;
using Compiled_Tests.Utils;
using Tedds_Tool.Automatables;
using CommonInterfaces.Models;
using Compiled_Tests;
using Tedds_Tool.RulesBase;
using LEET.StandardModuals.Instructions;
using LEET.CommonInterfaces.Models;
using RulesBase.Model;
using LEET.Utilities;
using LEET.Application.Compilers.LEET;
using LEET.LEETCommon;
using GUI.Custom_Classes;

namespace Tedds_Tool
{
    public class Compiler
    {
        private IInstructionFactory Factory;
        private ICompilerConfig config;

        // uses default settings
        public Compiler(IInstructionFactory factory) : this(factory, new SubTestConfig()) { }
        
        public Compiler(IInstructionFactory factory, ICompilerConfig config)
        {
            Factory = factory;
            this.config = config;
        }

        public String Compile(IScript script)
        {
            return Compile(script, config);
        }

        public String Compile(IScript script, ICompilerConfig config)
        {
            StringBuilder buf = new StringBuilder();

            buf.Append(RenderHeader());
            buf.Append(RenderScriptClassHeader(script.Name, "I"+config.ScriptInterface.ToString()));

            if (config.IncludeTestMethod)
                buf.Append(RenderTestMethod(script.Name));
            if (config.IncludeMainMethod)
                buf.Append(RenderMainMethod(script.Name, config.TestController.ToString()));

            IList<IInstruction> instructions = new List<IInstruction>();

            foreach (String line in script)
                if (!string.IsNullOrEmpty(line.Trim()))
                {
                    instructions.Add(Factory.Interpret(line));
                }

            if (instructions.Count <= 0)
                throw new InvalidScriptException("Could not compile empty script " + script.Name);

            buf.Append(RenderNewRunMethod(instructions));
            buf.Append(RenderCommentedScript(instructions));
            
            buf.Append(RenderFooter());

            return buf.ToString();

        }

        private String RenderNewRuleRunMethod(IRule rule)
        {
            StringBuilder buf = new StringBuilder();

            buf.Append("        public IEnumerable<String> Run(IBox box)\r\n");
            buf.Append("        {\r\n");
            buf.Append("            this.Box = box;\r\n");
            buf.Append("            bool preconditions_met = true;\r\n");

            // preconditions
            buf.Append("            try\r\n");
            buf.Append("            {\r\n");
            foreach (IInstruction ins in rule.PreconditionInstructions)
            {
                buf.Append("                ")
                    .Append(ins.RenderStaticCall())
                    .Append(";\r\n");
            }
            buf.Append("            }\r\n");
            buf.Append("            catch\r\n");
            buf.Append("            {\r\n");
            buf.Append("                preconditions_met = false;\r\n");
            buf.Append("            }\r\n");

            //////////////////////////////////
            buf.Append("            if(preconditions_met)\r\n");
            buf.Append("            {\r\n");

            // implications
            buf.Append("                try\r\n");
            buf.Append("                {\r\n");
            foreach (IInstruction ins in rule.ImplicationInstructions)
            {
                buf.Append("                    ")
                    .Append(ins.RenderStaticCall())
                    .Append(";\r\n");
            }
            buf.Append("                }\r\n");
            buf.Append("                catch\r\n");
            buf.Append("                {\r\n");
            // consequences
            buf.Append("                    IList<String> consequences = new List<String>();\r\n");
            foreach (IConsequence consequence in rule.Consequences)
                buf.Append("                    consequences.Add(\"").Append(Utilities.Escape(consequence.Message)).Append("\");\r\n");
            buf.Append("                    return consequences;\r\n");
            buf.Append("                }\r\n");

            buf.Append("            }\r\n");
            buf.Append("            return new List<String>();\r\n");
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        private String RenderNewRunMethod(IList<IInstruction> instructions)
        {
            StringBuilder buf = new StringBuilder();
            int counter = 0;

            buf.Append("        public IEnumerable<IAutomationActionResult> Run(IBox box)\r\n");
            buf.Append("        {\r\n");
            buf.Append("            this.Box = box;\r\n");
            foreach (IInstruction ins in instructions)
            {
                buf.Append("            yield return ")
                    .Append(ins.RenderStaticCall())
                    .Append(";\r\n");
                counter++;
            }
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        public String Compile(IRule rule)
        {
            return Compile(rule, config);
        }

        public String Compile(IRule rule, ICompilerConfig config)
        {
            StringBuilder buf = new StringBuilder();

            buf.Append(RenderHeader());
            buf.Append(RenderRuleClassHeader(rule.Name, "IExecutableRule"));
            buf.Append(RenderNewRuleRunMethod(rule));
            buf.Append(RenderFooter());

            return buf.ToString();
        }

        internal virtual StringBuilder RenderCommentedScript(IList<IInstruction> instructions)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append("\r\n");
            buf.Append("/**\r\n");
            buf.Append("Original Script:\r\n");
            buf.Append("\r\n");
            foreach (IInstruction ins in instructions)
                buf.Append(ins.ToString()).Append("\r\n");
            buf.Append("*/\r\n");
            return buf;
        }

        internal String RenderTestMethod(String scriptName)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append("        [TestMethod]\r\n");
            buf.Append("        public void Test_").Append(scriptName).Append("()\r\n");
            buf.Append("        {\r\n");
            buf.Append("            Main();\r\n");
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        internal String RenderMainMethod(string scriptName, string controller)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append("        [STAThread]\r\n");
            buf.Append("        public static void Main() \r\n");
            buf.Append("        {\r\n");
            buf.Append("            ").Append(scriptName).Append(" instance = new ").Append(scriptName).Append("();\r\n");
            buf.Append("            IController controller = new " + controller + "(instance);\r\n");
            buf.Append("            controller.Run();\r\n");
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        internal virtual String RenderHeader()
        {
            return ""+
@"using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Windows.Automation;
using System.Diagnostics;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Compiled_Tests;
using Compiled_Tests.Utils;
using Compiled_Tests.ProcessWrappers;
using RB = Compiled_Tests.RuleBase;
using LEET.CommonInterfaces.TestControls;
using LEET.LEETCommon.WidgetLookup;

namespace LEET.Tests
{
";
        }

        internal virtual String RenderScriptClassHeader(String scriptName, string interfase)
        {
            return "    [TestClass]\r\n"+RenderRuleClassHeader(scriptName, interfase);
        }

        internal virtual String RenderRuleClassHeader(String scriptName, String interfase)
        {
            return @"    public class " + Helpers.NormalizeClassName(scriptName) + @" : " + interfase + @"
    {
        private IBox Box;

";
        }

        internal virtual String RenderFooter()
        {
            return @"
    }
}
";
        }
    }
}
